package org.dromara.web.controller

import cn.dev33.satoken.annotation.SaIgnore
import cn.dev33.satoken.exception.NotLoginException
import cn.dev33.satoken.stp.StpUtil
import cn.hutool.core.codec.Base64
import cn.hutool.core.collection.CollUtil
import cn.hutool.core.util.ObjectUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.http.HttpServletRequest
import me.zhyd.oauth.utils.AuthStateUtils
import org.dromara.common.core.constant.SystemConstants
import org.dromara.common.core.domain.R
import org.dromara.common.core.domain.model.LoginBody
import org.dromara.common.core.domain.model.RegisterBody
import org.dromara.common.core.domain.model.SocialLoginBody
import org.dromara.common.core.utils.*
import org.dromara.common.encrypt.annotation.ApiEncrypt
import org.dromara.common.json.utils.JsonUtils
import org.dromara.common.ratelimiter.annotation.RateLimiter
import org.dromara.common.ratelimiter.enums.LimitType
import org.dromara.common.satoken.utils.LoginHelper
import org.dromara.common.social.config.properties.SocialProperties
import org.dromara.common.social.utils.SocialUtils
import org.dromara.common.sse.dto.SseMessageDto
import org.dromara.common.sse.utils.SseMessageUtils
import org.dromara.common.tenant.helper.TenantHelper
import org.dromara.system.domain.bo.SysTenantBo
import org.dromara.system.service.ISysClientService
import org.dromara.system.service.ISysConfigService
import org.dromara.system.service.ISysSocialService
import org.dromara.system.service.ISysTenantService
import org.dromara.web.domain.vo.LoginTenantVo
import org.dromara.web.domain.vo.LoginVo
import org.dromara.web.domain.vo.TenantListVo
import org.dromara.web.service.IAuthStrategy
import org.dromara.web.service.SysLoginService
import org.dromara.web.service.SysRegisterService
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit

/**
 *@author LikeYouDo
 *@date 2024/12/31 16:30
 */
@SaIgnore
@RestController
@RequestMapping("/auth")
class AuthController(
    private val socialProperties: SocialProperties,
    private val loginService: SysLoginService,
    private val registerService: SysRegisterService,
    private val configService: ISysConfigService,
    private val tenantService: ISysTenantService,
    private val socialUserService: ISysSocialService,
    private val clientService: ISysClientService,
    private val scheduledExecutorService: ScheduledExecutorService,
) {

    companion object {
        private val log = KotlinLogging.logger {}
    }

    /**
     * 登录方法
     *
     * @param body 登录信息
     * @return 结果
     */
    @ApiEncrypt
    @PostMapping("/login")
    fun login(@RequestBody body: String): R<out LoginVo?> {
        val loginBody = JsonUtils.parseObject(body, LoginBody::class.java)
        ValidatorUtils.validate(loginBody)
        // 授权类型和客户端id
        val clientId = loginBody.clientId
        val grantType = loginBody.grantType
        val client = clientService.queryByClientId(clientId)
        // 查询不到 client 或 client 内不包含 grantType
        if (!StringUtils.contains(client.grantType, grantType)) {
            log.info { "客户端id: $clientId 认证类型：$grantType 异常!." }
            return R.fail(MessageUtils.message("auth.grant.type.error"))
        } else if (SystemConstants.NORMAL != client.status) {
            return R.fail(MessageUtils.message("auth.grant.type.blocked"))
        }
        // 校验租户
        loginService.checkTenant(loginBody.tenantId)
        // 登录
        val loginVo = IAuthStrategy.login(body, client, grantType)

        val userId = LoginHelper.getUserId()
        scheduledExecutorService.schedule({
            val dto = SseMessageDto()
            dto.message = "欢迎登录RuoYi-Vue-Plus后台管理系统"
            dto.userIds = listOf(userId)
            SseMessageUtils.publishMessage(dto)
        }, 5, TimeUnit.SECONDS)
        return R.ok(loginVo)
    }

    /**
     * 获取跳转URL
     *
     * @param source 登录来源
     * @return 结果
     */
    @GetMapping("/binding/{source}")
    fun authBinding(
        @PathVariable("source") source: String,
        @RequestParam tenantId: String,
        @RequestParam domain: String
    ): R<String?> {
        val obj = socialProperties.type[source]
        if (ObjectUtil.isNull(obj)) {
            return R.fail(source + "平台账号暂不支持")
        }
        val authRequest = SocialUtils.getAuthRequest(source, socialProperties)
        val map: MutableMap<String, String> = HashMap()
        map["tenantId"] = tenantId
        map["domain"] = domain
        map["state"] = AuthStateUtils.createState()
        val authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8))
        return R.ok("操作成功", authorizeUrl)
    }

    /**
     * 前端回调绑定授权(需要token)
     *
     * @param loginBody 请求体
     * @return 结果
     */
    @PostMapping("/social/callback")
    fun socialCallback(@RequestBody loginBody: SocialLoginBody): R<Void?> {
        // 校验token
        StpUtil.checkLogin()
        // 获取第三方登录信息
        val response = SocialUtils.loginAuth(
            loginBody.source, loginBody.socialCode,
            loginBody.socialState, socialProperties
        )
        val authUserData = response.data
        // 判断授权响应是否成功
        if (!response.ok()) {
            return R.fail(response.msg)
        }
        loginService.socialRegister(authUserData)
        return R.ok()
    }

    /**
     * 取消授权(需要token)
     *
     * @param socialId socialId
     */
    @DeleteMapping(value = ["/unlock/{socialId}"])
    fun unlockSocial(@PathVariable("socialId") socialId: Long): R<Void?> {
        // 校验token
        StpUtil.checkLogin()
        val rows = socialUserService.deleteWithValidById(socialId)
        return if (rows) R.ok() else R.fail("取消授权失败")
    }

    /**
     * 退出登录
     */
    @PostMapping("/logout")
    fun logout(): R<Void?> {
        loginService.logout()
        return R.ok("退出成功")
    }

    /**
     * 用户注册
     */
    @ApiEncrypt
    @PostMapping("/register")
    fun register(@Validated @RequestBody user: RegisterBody): R<Void?> {
        if (!configService.selectRegisterEnabled(user.tenantId)) {
            return R.fail("当前系统没有开启注册功能！")
        }
        registerService.register(user)
        return R.ok()
    }

    /**
     * 登录页面租户下拉框
     *
     * @return 租户列表
     */
    @RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
    @GetMapping("/tenant/list")
    @Throws(Exception::class)
    fun tenantList(request: HttpServletRequest): R<LoginTenantVo> {
        // 返回对象
        val result = LoginTenantVo()
        val enable = TenantHelper.isEnable()
        result.tenantEnabled = enable
        // 如果未开启租户这直接返回
        if (!enable) {
            return R.ok(result)
        }

        val tenantList = tenantService.queryList(SysTenantBo())
        val voList = MapstructUtils.convert(
            tenantList,
            TenantListVo::class.java
        )
        try {
            // 如果只超管返回所有租户
            if (LoginHelper.isSuperAdmin()) {
                result.voList = voList
                return R.ok(result)
            }
        } catch (ignored: NotLoginException) {
        }

        val referer = request.getHeader("referer")
        // 获取域名
        val host = if (StringUtils.isNotBlank(referer)) {
            // 这里从referer中取值是为了本地使用hosts添加虚拟域名，方便本地环境调试
            referer.split("//")[1].split("/")[0]
        } else {
            URL(request.requestURL.toString()).host
        }
        // 根据域名进行筛选
        val list = StreamUtils.filter(voList) { vo ->
            StringUtils.equalsIgnoreCase(vo.domain, host)
        }
        result.voList = if (CollUtil.isNotEmpty(list)) list else voList
        return R.ok(result)
    }
}
